﻿using System;
using System.Diagnostics;

namespace EvoBoost.Core
{
	[Serializable]
	public class MinMaxTruncSplitParameterDescription : ISplitParameterDescription
	{
		private static readonly Random random = new Random();

		public MinMaxTruncSplitParameterDescription(double minValue, double maxValue, int steps, int mutationDeviationSteps)
		{
			if (maxValue < minValue)
				throw new ArgumentOutOfRangeException("maxValue", "Max value should not be less then min value.");
			if (steps <= 0)
				throw new ArgumentOutOfRangeException("steps", "Step count should be positive.");
			if (mutationDeviationSteps <= 0)
				throw new ArgumentOutOfRangeException("mutationDeviationSteps", "Mutation deviation should be positive");

			this.MinValue = minValue;
			this.MaxValue = maxValue;
			this.Steps = steps;
			this.MutationDeviationSteps = mutationDeviationSteps;
		}

		public double MinValue { get; private set; }

		public double MaxValue { get; private set; }

		public int Steps { get; private set; }

		public int MutationDeviationSteps { get; private set; }

		public double GenerateRandom()
		{
			double stepLength = (this.MaxValue - this.MinValue) / this.Steps;
			int steps = random.Next(this.Steps + 1);
			double result = this.MinValue + stepLength * steps;
			Debug.Assert(result >= this.MinValue - 1e-9 && result <= this.MaxValue + 1e-9);
			return result;
		}

		public double Perturb(double oldValue)
		{
			if (oldValue < this.MinValue || oldValue > this.MaxValue)
				throw new ArgumentOutOfRangeException("oldValue");
			
			// Calculate amount of random steps to apply
			double stepLength = (this.MaxValue - this.MinValue) / this.Steps;
			int steps = random.Next(2 * this.MutationDeviationSteps + 1) - this.MutationDeviationSteps;

			// Calculate result and clamp
			double result = oldValue + steps * stepLength;
			result = Math.Min(Math.Max(result, this.MinValue), this.MaxValue);
			return result;
		}
	}
}
